/**
 * 
 */
package mdp.id;

import java.io.File;
import java.io.IOException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;

import logic.add.DD;
import mdp.id.RDDL2DD.DEBUG;

import rddl.ActionGenerator;
import rddl.EvalException;
import rddl.RDDL;
import rddl.RDDL.Bernoulli;
import rddl.RDDL.CPF_DEF;
import rddl.RDDL.DOMAIN;
import rddl.RDDL.EXPR;
import rddl.RDDL.INSTANCE;
import rddl.RDDL.LCONST;
import rddl.RDDL.LTERM;
import rddl.RDDL.LTYPED_VAR;
import rddl.RDDL.LVAR;
import rddl.RDDL.NONFLUENTS;
import rddl.RDDL.PVARIABLE_DEF;
import rddl.RDDL.PVAR_INST_DEF;
import rddl.RDDL.PVAR_NAME;
import rddl.RDDL.TYPE_NAME;
import rddl.State;
import rddl.competition.Client;
import rddl.parser.parser;
import rddl.policy.Policy;
import rddl.viz.ElevatorDisplay;
import rddl.viz.StateViz;
import rddl.viz.TrafficDisplay;
import util.Pair;
import rddl.RDDL.KronDelta;
import rddl.RDDL.PVAR_EXPR;
import rddl.RDDL.AGG_EXPR;
import rddl.RDDL.OPER_EXPR;
import rddl.RDDL.COMP_EXPR;
import rddl.RDDL.INT_CONST_EXPR;
import rddl.RDDL.BOOL_EXPR;
import rddl.RDDL.BOOL_CONST_EXPR;
import rddl.RDDL.IF_EXPR;
import rddl.RDDL.DiracDelta;
import rddl.RDDL.REAL_CONST_EXPR;
/**
 * @author NR
 *
 * TODO
 */
public abstract class LogicRDDL2DD<D extends DD>{
	
	public enum DEBUG{
		PROBLEM_INFO, SOLUTION_INFO, DIAGRAMS
	}
	
	public enum DEBUG_LEVEL{
		PROBLEM_INFO, SOLUTION_INFO, DIAGRAMS 
	}
	
	public enum ORDER{
		XAXP, AXXP, XPAX, XXPA, 
		AXPX, XPXA, GUESS, INTERLEAVE 
	}
	
	public static String CleanFluentName(String s) {
		s = s.replace("[", "__");
		s = s.replace("]", "");
		s = s.replace(", ", "_");
		s = s.replace(',','_');
		s = s.replace(' ','_');
		s = s.replace('-','_');
		s = s.replace("()","");
		s = s.replace("(", "__");
		s = s.replace(")", "");
		if (s.endsWith("__"))
			s = s.substring(0, s.length() - 2);
		return s;
	};
	
	public DEBUG _debug;
	public State      _state = new State();
	public D         _context;
	private ArrayList<Integer> _alSaveNodes;

	public int         DD_ZERO;
	public int         DD_ONE;
	protected TreeMap<String, Pair<PVAR_NAME, ArrayList<LCONST> > > _tmStateVars = new TreeMap<String, Pair<PVAR_NAME,ArrayList<LCONST>>>();
	protected TreeMap<String, Pair<PVAR_NAME, ArrayList<LCONST> > > _tmNextStateVars = new TreeMap<String, Pair<PVAR_NAME,ArrayList<LCONST>>>();
	protected TreeMap<String, Pair<PVAR_NAME, ArrayList<LCONST> > > _tmActionVars = new TreeMap<String, Pair<PVAR_NAME,ArrayList<LCONST>>>();
	
	protected TreeMap<String, Pair<PVAR_NAME, ArrayList<LCONST> > > _tmObservVars = new TreeMap<String, Pair<PVAR_NAME,ArrayList<LCONST>>>();
	protected HashMap _hmPrimeRemap = new HashMap();
	public ArrayList<String> _alactionvars = new ArrayList<String>();
	protected ArrayList<String> _alstatevars = new ArrayList<String>();
	
	public Map<String, ArrayList<String> > _hmact2vars = new HashMap<String, ArrayList<String> >();
	public HashMap<String, ArrayList<String> > _hmvars2act = new HashMap<String, ArrayList<String>>();
	private ArrayList<String> _alnextstatevars = new ArrayList<String>();
	protected ArrayList<String> _alObservvars = new ArrayList<String>();
	
	private TreeMap<Integer, Integer> _var2transDD = new TreeMap<Integer, Integer>();
	
	private TreeMap<Integer, Integer> _var2observDD = new TreeMap<Integer, Integer>();
	
	protected final Runtime RUNTIME = Runtime.getRuntime();

	protected ORDER _order;

	protected ArrayList<Integer> _alOrder = new ArrayList<Integer>();

	protected TreeMap<PVAR_NAME, ArrayList<ArrayList<LCONST>>>	_state_vars;
	
	protected TreeMap<PVAR_NAME, ArrayList<ArrayList<LCONST>>>	_action_vars;
	
	protected TreeMap<PVAR_NAME, ArrayList<ArrayList<LCONST>>>	_observ_vars;

	/**
	 * @return the rewards
	 */
	public ArrayList<Integer> getRewards() {
		return this._rewards;
	}
	
	public TreeMap<String, Integer> _tmVarName2ID = new TreeMap<String, Integer>();
	
	public TreeMap<Integer, String> _tmID2VarName = new TreeMap<Integer, String>();

	private boolean	_bCPFDeterministic;

	protected HashSet<Pair<Integer,Integer>> _hsPairConstraints = new HashSet<Pair<Integer,Integer>>();

	protected int	_rewardDD = DD_ZERO;

	protected ArrayList<Integer>	_rewards;
	
	public boolean	_bRewardActionDependent = false;

	protected ArrayList<Integer>	_sumOrder = new ArrayList<Integer>();
	protected ArrayList<Integer>	_elimOrder = new ArrayList<Integer>();
	private Map<Long, Map<Integer, Boolean>>	_hmjointActionMap = new HashMap<Long, Map<Integer,Boolean>>();
	private Map<Map<Integer, Boolean>, Long> _hmActionIDCache = new HashMap<Map<Integer,Boolean>, Long>();
	private Map<String, Map<Integer, Boolean>> _hmJointActions = new HashMap<String, Map<Integer,Boolean>>() ;
	public int	DD_NEG_INF;
	private int	_valueDD;
	private int	_policyDD;
	private INSTANCE	_i;
	private NONFLUENTS	_n;
	private DOMAIN	_d;
	protected long	timetoplay;
	
	private ArrayList<Pair<Double, Double>> rewards;
	
//	public double	cacheFlushTime = 0;
	
	Map< Map<Integer, Boolean> , Map<Integer, Integer> > bigBookOfTrans 
		= new HashMap<Map<Integer,Boolean>, Map<Integer,Integer>>();

	Map< Map<Integer, Boolean> , Integer> bigBookOfRewards 
		= new HashMap<Map<Integer,Boolean>, Integer>();
	
	private boolean	BN_ON = false;


	private void addConcurrencyConstraint() throws Exception {
		
		EXPR concexpr = null;
		
		int xid = 1;
		for( Map.Entry<PVAR_NAME, ArrayList<ArrayList<LCONST>>> entry : _action_vars.entrySet() ){
			
			PVAR_NAME pvar = entry.getKey();
			PVARIABLE_DEF pvardefn = _state._hmPVariables.get(pvar);
			
			ArrayList<LTYPED_VAR> sumover = new ArrayList<LTYPED_VAR>();
			ArrayList<LTERM> subs = new ArrayList<LTERM>();
			
			ArrayList<TYPE_NAME> params = pvardefn._alParamTypes;
			int yid = 1;
			for( TYPE_NAME type : params ){
				String varname = "x"+(xid)+(yid);
				sumover.add(new LTYPED_VAR(varname, type._STypeName ));
				subs.add(new LVAR(varname));
				++yid;
			}
			
			PVAR_EXPR inner = new PVAR_EXPR(pvar.toString(), subs);
			EXPR summer = ( sumover.size() == 0 ) ? inner : new AGG_EXPR(AGG_EXPR.SUM, sumover, inner);
			
			concexpr = ( concexpr == null ) ? summer : new OPER_EXPR(summer, concexpr, OPER_EXPR.PLUS);
			++xid;
		}
		
		concexpr = new COMP_EXPR(concexpr, new INT_CONST_EXPR(_state._nMaxNondefActions), COMP_EXPR.LESSEQ);
		
		addConstraint((BOOL_EXPR)concexpr);
		
	}
	
	
	private void addConstraint(BOOL_EXPR be) throws Exception {
		HashMap<LVAR, LCONST> empty = new HashMap<LVAR, LCONST>();
		HashSet<Pair> gfluents = new HashSet<Pair>();
		be.collectGFluents(empty, _state, gfluents);
		
		IF_EXPR ife = new IF_EXPR(be, new KronDelta(new BOOL_CONST_EXPR(true)), 
				new KronDelta(new BOOL_CONST_EXPR(false)) );
		
		int zerodd = enumerateAssignments(new ArrayList<Pair>(gfluents), ife, empty	, 0 );
		
		empty.clear();
		
		int neginfdd = makeNegInfDD(zerodd, 0.0d);
	
		_hsPairConstraints.add(new Pair<Integer,Integer>(zerodd,neginfdd));
		
		if( _debug.compareTo(DEBUG.DIAGRAMS) == 0 ) {
			System.out.println("Displaying constraint : " + be);
			showGraph(zerodd);
			showGraph(neginfdd);
			System.out.println("Press key...");
			System.in.read();
		}
		
		
	}

private void add2InfluenceMap(String cpt_var, HashSet<Pair> relevant_vars, TreeMap<PVAR_NAME, ArrayList<ArrayList<LCONST>>> action_vars) {
		
		for( Pair pair : relevant_vars ){
			PVAR_NAME pvar = (PVAR_NAME)pair._o1;
			ArrayList<LCONST> term = (ArrayList<LCONST>)pair._o2;
			
//			System.out.println(pvar.toString() + " " + term + " " + (action_vars.get(pvar)!=null) );
			
			if( action_vars.get(pvar) != null ){
				
				String actname = CleanFluentName(pvar.toString() + term);
				
				if( _hmact2vars.get(actname) == null  ){
					_hmact2vars.put(actname, new ArrayList<String>());
				}
				
				_hmact2vars.get(actname).add(cpt_var);
				
				if( _hmvars2act .get(cpt_var) == null  ){
					_hmvars2act.put(cpt_var, new ArrayList<String>());
				}
				
				_hmvars2act.get(cpt_var).add(actname);
				
				
			}
			
		}

//		System.exit(1);
		
	}

	/**
	 * @param pZerodd
	 * @return
	 */
	protected abstract int makeNegInfDD(int dd, double val);


	/**
	 * @throws Exception 
	 * 
	 */
	private void buildCPTs(boolean withActionVars) throws Exception {

		if( !withActionVars ){
			BN_ON = true;
		}
		
		EXPR rew_expr =  _state._reward;
		
		HashSet<Pair> rewardRelVars = new HashSet<Pair>();
			
		rew_expr.collectGFluents(new HashMap<LVAR, LCONST>(), _state, rewardRelVars );
		
		for( Pair piar : rewardRelVars ){
			PVAR_NAME thing = (PVAR_NAME)piar._o1;
			if( _state._alActionNames.contains(thing) ){
				_bRewardActionDependent = true;
				break;
			}
		}

		for( EXPR constraint : _state._alConstraints ){
			if( constraint instanceof BOOL_EXPR ){
				BOOL_EXPR be = (BOOL_EXPR)constraint;
				addConstraint(be);
				flushCaches(false);
			}else{
				System.err.println("Constraint not tpye of bool expr");
				System.exit(1);
			}
		}
		
		ArrayList<Map<Integer, Boolean>> acts = getFullRegressionOrder();
			
		for( int act = 0 ; (act == 0) || (!withActionVars && act < acts.size() ); ++act ){
		
			if( !withActionVars && _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ){
				System.out.println("Building CPT for action : " + acts.get(act) );
				for( Map.Entry<Integer, Boolean> ent : acts.get(act).entrySet() ){
					System.out.println( getVarName(ent.getKey()) + " " + ent.getValue() );
				}
			}
				
			Map<Integer, Integer> actionTransition = null;
			if( !withActionVars ){
				actionTransition = new HashMap<Integer, Integer>();
			}

			for (int iter = 0; iter <= 1; iter++) {
				
				TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> src = 
					iter == 0 ? _state_vars : _observ_vars;
				
				for (Map.Entry<PVAR_NAME,ArrayList<ArrayList<LCONST>>> e : src.entrySet()) {
					
					// Go through all variable names p for a variable type
					PVAR_NAME p = e.getKey();
					ArrayList<ArrayList<LCONST>> assignments = e.getValue();
		                                                                                            			
					CPF_DEF cpf = _state._hmCPFs.get(new PVAR_NAME(p.toString() + 
							(iter == 0 ? "'" : "")));
					
					HashMap<LVAR,LCONST> subs = new HashMap<LVAR,LCONST>();
					for (ArrayList<LCONST> assign : assignments) {
		
						String cpt_var = CleanFluentName(p.toString() + assign);
						
						if( _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ) {
							System.out.println("Processing: " + cpt_var + "'");
						}
		
						subs.clear();
						for (int i = 0; i < cpf._exprVarName._alTerms.size(); i++) {
							LVAR v = (LVAR)cpf._exprVarName._alTerms.get(i);
							LCONST c = (LCONST)assign.get(i);
							subs.put(v,c);
						}
						
						HashSet<Pair> relevant_vars = new HashSet<Pair>();
						EXPR cpf_expr = cpf._exprEquals;
						
						if (_bCPFDeterministic) // collectGFluents expects a distribution so convert to a Delta function if needed
							cpf_expr = new KronDelta(cpf_expr);					
						
						cpf_expr.collectGFluents(subs, _state, relevant_vars);
						
						if( !withActionVars ){
							Map<Integer, Boolean> action = acts.get(act);
							setState(action);
							relevant_vars = removeActionVars(relevant_vars);
						}
						
						add2InfluenceMap(cpt_var+"'", relevant_vars, _action_vars);
						
						if( _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ) {
							System.out.println("Relevant variables : " + relevant_vars);
						}
						
						int cpt = enumerateAssignments(new ArrayList<Pair>(relevant_vars), cpf_expr, subs, 0);
						
						//build dual diagram
						int unprime_id = toContextID(cpt_var);
						int prime_id = toContextID(cpt_var+"'");
							
						int prob_true = cpt;
							
						int prob_false = _context.applyInt(DD_ONE, prob_true, DD.ARITH_MINUS);
							
						int high_branch = _context.getVarNode(prime_id, 0.0d, 1.0d);
							
						int low_branch = _context.getVarNode(prime_id, 1.0d, 0.0d);
							
						int low = _context.applyInt(low_branch, prob_false, DD.ARITH_PROD);
							
						int high = _context.applyInt(high_branch, prob_true, DD.ARITH_PROD);
							
						cpt = _context.applyInt(high, low,DD.ARITH_SUM);
						
	//					_alSaveNodes.add(cpt);
						
						if( withActionVars ){
							if (iter == 0) {
								_var2transDD.put(_tmVarName2ID.get(cpt_var+"'"), cpt);
							}else {
								_var2observDD.put( _tmVarName2ID.get(cpt_var+"'"), cpt);
							}
						}else{
							actionTransition.put(_tmVarName2ID.get(cpt_var+"'"), cpt);
						}
						
						CollectionFlushCaches(actionTransition.values());
						
						if( _debug.compareTo(DEBUG.DIAGRAMS) == 0 ) {
							System.out.println("Displaying CPT for " + cpt_var+"'");
							showGraph(cpt);
							System.out.println("Press any key");
							System.in.read();
						}
					}
				}
				
			}
				
			bigBookOfTrans.put(acts.get(act), actionTransition);
				
			////

			if( !withActionVars ){
				rew_expr.collectGFluents(new HashMap<LVAR, LCONST>(), _state, rewardRelVars );
				rewardRelVars = removeActionVars(rewardRelVars);
			}

			if( _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ){
				System.out.println("Reward relevant vars : " + rewardRelVars );
			}
				
			try {
				_rewards = convertAddExpr2ADD(rew_expr, false);
				_rewardDD = DD_ZERO;
				for( int subd : _rewards ){
					_rewardDD = _context.applyInt(_rewardDD, subd, DD.ARITH_SUM);
					CollectionFlushCaches(_rewards, _rewardDD);						
				}
					
				if( _debug.compareTo(DEBUG.DIAGRAMS) == 0 ) {
					System.out.println("Displaying reward");
					showGraph(_rewardDD);
					System.out.println("Press key");
					System.in.read();
				}

					
			} catch (Exception e) {
				e.printStackTrace();
			}		

			if( !withActionVars ){
				bigBookOfRewards.put(acts.get(act), _rewardDD);
			}
			flushCaches(false);
		}
//		translateReward(withActionVars);
	}
	
	private void translateReward(boolean withActionVars) {
		
		double rmin;
		
		if( withActionVars ){
		
			//get rmin
			rmin = Math.abs(getMin(_rewardDD));
//				System.out.println(" Adding = " + (1+rmin) );
				//get constant node
//				_rewardDD = applyConstraintsInterleaved(_rewardDD);
//				_context.addSpecialNode(_rewardDD);
					
		}else{
			
			int minRew = DD_NEG_INF;
			for( int r : bigBookOfRewards.values() ){
				minRew = _context.applyInt( minRew, r, DD.ARITH_MIN );	
			}
			rmin = Math.abs(getMin(_rewardDD));
		}
		
		int shiftdd = _context.getConstantNode(1+rmin);
		//add to reward
		
		if( withActionVars ){
			_rewardDD = _context.applyInt(_rewardDD, shiftdd, DD.ARITH_SUM );
			_rewards.add(shiftdd);
		}else{
			
			for( Map.Entry<Map<Integer, Boolean>, Integer> entry : bigBookOfRewards.entrySet() ){
				int shifted = _context.applyInt(entry.getValue(), shiftdd, DD.ARITH_SUM );
				entry.setValue(shifted);
			}
			
		}
		
		flushCaches(false);

	}


	private HashSet<Pair> removeActionVars(HashSet<Pair> vars) {
		
		HashSet<Pair> ret = new HashSet<Pair>();
		
		for( Pair p : vars ){
			PVAR_NAME pv = (PVAR_NAME)p._o1;
			if( _state._alActionNames.contains(pv) ){
				continue;
			}else{
				ret.add(p);
			}
		}
		
		return ret;
	}


	private void unsetState(Map<Integer, Boolean> assignment) {
		
		for( Map.Entry<Integer, Boolean> assign : assignment.entrySet() ){
			String name = getVarName(assign.getKey());
			
			PVAR_NAME pvar = getPVar(name);
			ArrayList<LCONST> terms = getTerms(pvar, name);
			
			_state.setPVariableAssign(pvar, terms, null );
			
		}
		
	}


	private void setState(Map<Integer, Boolean> assignment) {
		
		for( Map.Entry<Integer, Boolean> assign : assignment.entrySet() ){
			String name = getVarName(assign.getKey());
			
			PVAR_NAME pvar = getPVar(name);
			ArrayList<LCONST> terms = getTerms(pvar, name);
			
			_state.setPVariableAssign(pvar, terms, assign.getValue() );
			
		}
		
	}


	private ArrayList<LCONST> getTerms(PVAR_NAME pvar, String name) {
		try {
			ArrayList<ArrayList<LCONST>> allTerms = _state.generateAtoms(pvar);
			for( ArrayList<LCONST> terms : allTerms ){
				if( CleanFluentName( pvar._sPVarName + terms ).equals(name) ){
					return terms;
				}
			}
			
		} catch (EvalException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return null;
		
	}


	private PVAR_NAME getPVar(String name) {
		
		name = name.toLowerCase();
		name = name.replace('_', '-');
//		System.out.println(name);
		
		for( PVAR_NAME state : _state._alActionNames){
			if( name.startsWith(state._sPVarName) ){
				return state;
			}
		}
		
		for( PVAR_NAME state : _state._alStateNames){
			if( state.toString().startsWith(name) ){
				return state;
			}
		}
		
		for( PVAR_NAME state : _state._alObservNames){
			if( state.toString().startsWith(name) ){
				return state;
			}
		}
		
		return null;
		
	}


	public void CollectionFlushCaches(Collection<Integer> values, final int... save) {
		Vector<Integer> saved = new Vector<Integer>(values);
		int[] savedd = new int[saved.size()+save.length];
		
		int i =0;
		
		for( i = 0; i < saved.size(); ++i ){
			savedd[i] = saved.get(i);
		}

		for( int j = 0; j < save.length; ++j ){
			savedd[i++] = save[j];
		}
		
		flushCaches(false, savedd);
		
	}
	
	//build cpt,reward and constraints as DDs
	public void buildFromFile(final String pDomain, final String pInstance, boolean withActionVars) {

		RDDL _rddltemp = new RDDL();
		
		File domf = new File(pDomain);
		File instf = new File(pInstance);
		
		RDDL domain = null;
		RDDL instance = null;
		
		try
		{
			domain = parser.parse(domf);
		} catch (Exception e)
		{
			System.err.println("domain file did not parse");
			e.printStackTrace();
			System.exit(1);
		}
		
		
		try
		{
			instance = parser.parse(instf);
		} catch (Exception e)
		{
			System.err.println("domain file did not parse");
			e.printStackTrace();
			System.exit(1);
		}
		
		buildFromRDDL(domain, instance, withActionVars);
	}
	/**
	 * @param pDomain
	 * @param pInstance
	 * @param withActionVars 
	 */
	private void buildFromRDDL(RDDL pDomain, RDDL pInstance, boolean withActionVars) {
		try {
			DOMAIN d = pDomain._tmDomainNodes.entrySet().iterator().next().getValue();
			INSTANCE i = pInstance._tmInstanceNodes.entrySet().iterator().next().getValue();
			NONFLUENTS n = pInstance._tmNonFluentNodes.entrySet().iterator().next().getValue();
			
			_bCPFDeterministic = d._bCPFDeterministic;
			
			this._i = i;
			this._n = n;
			this._d = d;
			
			
			initializeState(i, d, n);
			assert (_state._tmIntermNames.size() == 0);
			initRDDLData();
			initADD();
			makePrimeRemap();
			makeOrders();
			buildCPTs(withActionVars);
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

	private TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> collectActionVars() 
		throws EvalException {
		
		TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> action_vars = 
			new TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>>();
	
		for (PVAR_NAME p : _state._alActionNames) {
			ArrayList<ArrayList<LCONST>> gfluents = _state.generateAtoms(p);
			action_vars.put(p, gfluents);
		}
		
		return action_vars;
	}
	
	private TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> collectObservationVars() 
		throws EvalException {
	
		TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> observ_vars = 
			new TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>>();
	
		for (PVAR_NAME p : _state._alObservNames) {
			ArrayList<ArrayList<LCONST>> gfluents = _state.generateAtoms(p);
			observ_vars.put(p, gfluents);
		}
		
		return observ_vars;
	}
	
	
	private TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> collectStateVars() throws EvalException {
	
		TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> state_vars = 
			new TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>>();

		for (PVAR_NAME p : _state._alStateNames) {
			ArrayList<ArrayList<LCONST>> gfluents = _state.generateAtoms(p);
			state_vars.put(p, gfluents);
		}
	
		return state_vars;
	}

	public abstract int ConstrainThis(int dd, Map<Integer, Boolean> act, boolean stateact, 
			boolean flush, final int... save);
	
	private ArrayList<Integer> convertAddExpr2ADD(EXPR e, boolean filter_actions) throws Exception {
		
		ArrayList<Integer> adds = new ArrayList<Integer>();
		ArrayList<Pair> exprs = getAdditiveComponents(e);
		
//		System.out.println("\n");
		for (Pair p : exprs) {
			String str = "";
			if (p._o2 instanceof RDDL.OPER_EXPR)
				str = ((RDDL.OPER_EXPR)p._o2)._op;
//			System.out.println("Found pair: " + p._o1 + " -- " + p._o2.getClass() + " / " + str + "\n" + p);
		}
		
		int ZERO_ADD = _context.getConstantNode(0d); 
		
		for (Pair p : exprs) {
			
			HashSet<Pair> relevant_vars = new HashSet<Pair>();
			HashMap subs = (HashMap)p._o1;
			EXPR e2 = ((EXPR)p._o2);
			if ( _d._bRewardDeterministic) // collectGFluents expects distribution
				e2 = new DiracDelta(e2);
			else{
//				System.out.println("WARNING: May not convert additive reward correctly... check results.");
			}
				
			e2.collectGFluents(subs, _state, relevant_vars);
			
//				System.out.println("  - relevant vars: " + relevant_vars);
			
			int add = enumerateAssignments(new ArrayList<Pair>(relevant_vars), e2, subs, 0);
			if (add != ZERO_ADD) {
				adds.add(add);
			}
		}
//		System.out.println("Done processing additive expression");
		
		return adds;
	}

	/**
	 * @param pOrder
	 */
	private void createIDs(ArrayList<String> pOrder) {
		_alOrder.clear();
		
		int id = 0;
		for( String s : pOrder ) {
			_tmVarName2ID.put(s, id);
			_tmID2VarName.put(id, s);
			_alOrder.add(id);
			++id;
		}
	}

	/**
	 * @return
	 */
	protected abstract D createInstance();
	
	private int enumerateAssignments(ArrayList<Pair> vars, 
			EXPR cpf_expr, HashMap<LVAR,LCONST> subs, int index) 
		throws Exception {
		return enumerateAssignments(vars, cpf_expr, subs, index, _context.getConstantNode(1d));
	}
	
	private int enumerateAssignments(ArrayList<Pair> vars, 
			EXPR cpf_expr, HashMap<LVAR,LCONST> subs, int index, int cpt) 
		throws EvalException, Exception {
	
		// Need to build an assignment ADD on the way down, use it 
		// at leaf to add to current CPT and return result
		
		// Recurse until index == vars.size
		if (index >= vars.size()) {
			
			// At this point, all state and action variables are set
			// Just need to get the distribution for the appropriate CPT
			RDDL.EXPR e = cpf_expr.getDist(subs, _state);
	//		System.out.println( _state + " " + e );
			double prob_true = -1d;
			//System.out.println("RDDL.EXPR: " + e);
			if (e instanceof KronDelta) {
				EXPR e2 = ((KronDelta)e)._exprIntValue;
				if (e2 instanceof INT_CONST_EXPR)
					// Should either be true (1) or false (0)... same as prob_true
					prob_true = (double)((INT_CONST_EXPR)e2)._nValue;
				else if (e2 instanceof BOOL_CONST_EXPR)
					prob_true = ((BOOL_CONST_EXPR)e2)._bValue ? 1d : 0d;
				else
					throw new EvalException("Unhandled KronDelta argument: " + e2.getClass()); 
			} else if (e instanceof Bernoulli) {
				prob_true = ((REAL_CONST_EXPR)((Bernoulli)e)._exprProb)._dValue;
			} else if (e instanceof DiracDelta) {
				// NOTE: this is not a probability, but rather an actual value
				//       (presumably for the reward).  This method is a little
				//       overloaded... need to consider whether there will be
				//       any problems arising from this overloading.  -Scott
				prob_true = ((REAL_CONST_EXPR)((DiracDelta)e)._exprRealValue)._dValue;
			} else
				throw new EvalException("Unhandled distribution type: " + e.getClass());
			
//			for( Pair p : vars ) {
//				PVAR_NAME pvar = (PVAR_NAME)p._o1;
//				ArrayList<LCONST> terms = (ArrayList<LCONST>)p._o2;
//				System.out.println(pvar.toString() +  " " + terms + " " + _state.getPVariableAssign(pvar, terms));
//			}
//			System.out.println(prob_true);
			
			// Now build CPT for action variables
			return _context.scalarMultiply(cpt, prob_true);
	
		} else {
	//		System.out.println(vars + " " + index);
			
			PVAR_NAME p = (PVAR_NAME)vars.get(index)._o1;
			ArrayList<LCONST> terms = (ArrayList<LCONST>)vars.get(index)._o2;
			String var_name = CleanFluentName(p._sPVarName + terms + (p._bPrimed ? "\'" : ""));
//			System.err.println(var_name );
			int var_id = toContextID(var_name);
			
	
			// Set to true
			_state.setPVariableAssign(p, terms, RDDL.BOOL_CONST_EXPR.TRUE);
//			System.out.println(index + " " + var_name);
//			System.out.println(true);
			int high = _context.getVarNode(var_id, 0d, 1d);
			int ret_high = enumerateAssignments(vars, cpf_expr, subs, index + 1, _context.applyInt(cpt, high, DD.ARITH_PROD));
//			System.out.println(_state);
			
//			System.out.println(_context.printNode(ret_high));
			
			// Set to false
			_state.setPVariableAssign(p, terms, RDDL.BOOL_CONST_EXPR.FALSE);
//			System.out.println(index + " " + var_name);
//			System.out.println(false);
			int low  = _context.getVarNode(var_id, 1d, 0d);
			int ret_low = enumerateAssignments(vars, cpf_expr, subs, index + 1, _context.applyInt(cpt, low, DD.ARITH_PROD));
//			System.out.println(_state);
			
//			System.out.println(_context.printNode(ret_low));
			
			int res = _context.applyInt(ret_high, ret_low, DD.ARITH_SUM);
			
//			System.out.println("res");
//			System.out.println(_context.printNode(res));
			
			// Unassign
			_state.setPVariableAssign(p, terms, null);
	//		_context.getGraph(res).launchViewer();
	//		System.in.read();
			// Sum both sides of the returned CPT and return it
			return res;
		}
	}
	
	/**
	 * @param pVar_name
	 * @return
	 */
	public abstract int toContextID(String varname);


	public void flushCaches(boolean show_info, final int... save) {
		_context.clearSpecialNodes();
		_context.addSpecialNode(DD_ZERO);
		_context.addSpecialNode(DD_ONE);
		_context.addSpecialNode(DD_NEG_INF);

		for( int special : _alSaveNodes ){
			_context.addSpecialNode(special);
		}
		
		if( BN_ON ) {
			
			for( Integer entry : 
					bigBookOfRewards.values() ){
				_context.addSpecialNode(entry);
			}
			
			for( Map.Entry< Map<Integer, Boolean> , Map<Integer, Integer> > entry : 
				bigBookOfTrans.entrySet() ){
				for( Integer dd : entry.getValue().values() ) {
					_context.addSpecialNode(dd);
				}
			}
			
		}else {
			_context.addSpecialNode(_rewardDD);
			for( Map.Entry<Integer, Integer> entry : _var2observDD.entrySet() ) {
				_context.addSpecialNode(entry.getValue());	
			}
			
			for( Map.Entry<Integer, Integer> entry : _var2transDD.entrySet() ) {
				_context.addSpecialNode(entry.getValue());
			}
			
			if( _rewards != null ) {
				for( int r : _rewards ) {
					_context.addSpecialNode(r);
				}				
			}
			
		}
		
		for( Pair<Integer,Integer> special : _hsPairConstraints ){
			_context.addSpecialNode(special._o1);
			_context.addSpecialNode(special._o2);
		}
		
		for( int special : save ){
			_context.addSpecialNode(special);
		}
			
//		_context.applyHit = 0;
//		System.out.println(_context._hsSpecialNodes.size());
		
//		long t = System.currentTimeMillis();
		_context.flushCaches(show_info);	
//		cacheFlushTime  += (System.currentTimeMillis()-t)/(1000d*60);
		
	}
	

	private ArrayList<Pair> getAdditiveComponents(EXPR e) throws Exception {

		ArrayList<Pair> ret = new ArrayList<Pair>();
		
		if (e instanceof OPER_EXPR && ((OPER_EXPR)e)._op == OPER_EXPR.PLUS) {

			OPER_EXPR o = (OPER_EXPR)e;

			//System.out.println("\n- Oper Processing " + o._e1);
			//System.out.println("\n- Oper Processing " + o._e2);
			
			ret.addAll(getAdditiveComponents(o._e1));
			ret.addAll(getAdditiveComponents(o._e2));
						
		} else if (e instanceof AGG_EXPR && ((AGG_EXPR)e)._op == AGG_EXPR.SUM) {
			
			AGG_EXPR a = (AGG_EXPR)e;
			
			ArrayList<ArrayList<LCONST>> possible_subs = _state.generateAtoms(a._alVariables);
			HashMap<LVAR,LCONST> subs = new HashMap<LVAR,LCONST>();

			//System.out.println("\n- Sum Processing " + a);

			// Evaluate all possible substitutions
			for (ArrayList<LCONST> sub_inst : possible_subs) {
				for (int i = 0; i < a._alVariables.size(); i++) {
					subs.put(a._alVariables.get(i)._sVarName, sub_inst.get(i));
				}
				
				// Note: we are not currently decomposing additive structure below a sum aggregator
				ret.add(new Pair(subs.clone(), a._e));			
				
				subs.clear();
			}

		} else {
			//System.out.println("\n- General Processing " + e);
			HashMap<LVAR,LCONST> empty_subs = new HashMap<LVAR,LCONST>();
			ret.add(new Pair(empty_subs, e));
		}
		
		return ret;
	}
	
	private ArrayList<Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST>>>>  getCptOrderSortedByActionVars() throws EvalException{
		ArrayList<Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST>>>> cptOrder = 
			new ArrayList<Pair<Integer,Pair<PVAR_NAME, ArrayList<LCONST> >>>();
		
		for (int iter = 0; iter <= 1; iter++) {
			
			TreeMap<PVAR_NAME,ArrayList<ArrayList<LCONST>>> src = 
				iter == 0 ? _state_vars : _observ_vars;
			
			for (Map.Entry<PVAR_NAME,ArrayList<ArrayList<LCONST>>> e : src.entrySet()) {
				
				PVAR_NAME p = e.getKey();
				ArrayList<ArrayList<LCONST>> assignments = e.getValue();
				
				CPF_DEF cpf = _state._hmCPFs.get(new PVAR_NAME(p.toString() + 
						(iter == 0 ? "'" : "")));
				
				HashMap<LVAR,LCONST> subs = new HashMap<LVAR,LCONST>();
				for (ArrayList<LCONST> assign : assignments) {
	
					String cpt_var = CleanFluentName(p.toString() + assign);
//					System.out.println("Processing: " + cpt_var);
					
	
					subs.clear();
					for (int i = 0; i < cpf._exprVarName._alTerms.size(); i++) {
						LVAR v = (LVAR)cpf._exprVarName._alTerms.get(i);
						LCONST c = (LCONST)assign.get(i);
						subs.put(v,c);
					}
					
					EXPR cpf_expr = cpf._exprEquals;
					
					HashSet<Pair> relvars = new HashSet<Pair>();
					cpf_expr.collectGFluents(subs, _state, relvars);
					
					int count = 0;
					
					for( Pair pa : relvars ){
						PVAR_NAME pvar = (PVAR_NAME)pa._o1;
						ArrayList<LCONST> ass = (ArrayList<LCONST>)pa._o2;
						String name = CleanFluentName(pvar.toString()+ass);
						
						if( _state.getPVariableType(pvar) == _state.ACTION ){
							++count;
						}
					}
					
					cptOrder.add(new Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST> > >(count, new Pair<PVAR_NAME, ArrayList<LCONST> >(p, assign)));
				}
			}
		}
	
//		System.out.println(cptOrder);
		
		Comparator<Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST>>>> cptComparator = 
			new Comparator<Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST>>>>() {
		
			public int compare(Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST>>> o1,
					Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST>>> o2) {
				
				if( o1._o1.compareTo(o2._o1) != 0 ){
					return o1._o1.compareTo(o2._o1);
				}else{
					if( o1._o2._o1.compareTo(o2._o2._o1) != 0 ){
						return o1._o2._o1.compareTo(o2._o2._o1);
					}else{
						return 0;
					}
				}
				
			}
		};
		
		Collections.sort(cptOrder, cptComparator);
//		Collections.reverse(cptOrder);
		return cptOrder;
	}

	public int getDualDiagram(int Xprimed, Map<Integer, Boolean> action) {
		if( BN_ON ) {
			return bigBookOfTrans.get(action).get(Xprimed);
		}else {
			int dd = _var2transDD.get(Xprimed);
			dd = simplify(dd, action);
			return dd;
		}
	}

	protected Map<Integer, Boolean> convertToInts(Map<String, Boolean> ActAssigns) {
		Map<Integer, Boolean> ret = new HashMap<Integer, Boolean>();
		
		for( Map.Entry<String, Boolean> entry : ActAssigns.entrySet() ) {
			ret.put(_tmVarName2ID.get(entry.getKey()), entry.getValue());
		}
		
		return ret;
	}
	
	public abstract String getActualVarName(int ddID);
	/**
	 * @param pAction
	 * @return
	 */
	public long getJointActionID(final Map<Integer, Boolean> assigns) {
		
		if( _hmActionIDCache.get(assigns) == null ){
			long key = computeInt(assigns);
			Map<Integer, Boolean> thing = new TreeMap<Integer, Boolean>(assigns);
			_hmjointActionMap.put(key, thing);	
			_hmActionIDCache.put(thing, key);
		}
		
		return _hmActionIDCache.get(assigns);
		
	}
	
	private long computeInt(final Map<Integer, Boolean> assigns) {
		long ret = 0;
		
		for( int i = 0 ; i < _alactionvars.size(); ++i ) {
			String act = _alactionvars.get(i);
			int id = _tmVarName2ID.get(act);
			Boolean defval = (Boolean)(_state.getDefaultValue(_tmActionVars.get(act)._o1));
			if ( assigns.get(id) != null && !defval.equals(assigns.get(id)) ) {
				ret += (1 << i);
			}
		}
		return ret;	
	}
	
	public abstract double getMax(int dd); 
		
	public ArrayList<Integer> getMaxOrder(){
		return _elimOrder;
	}
	
	public abstract double getMin(int dd);
	
	public abstract double getNegInf();
	
	public int getReward(Map<Integer, Boolean> pAct) {
		if( BN_ON ) {
			return bigBookOfRewards.get(pAct);
		}else {
			return simplify(_rewardDD, pAct);	
		}
	}
	
	public boolean getRewardActionDependent() {
		return _bRewardActionDependent;
	}

	public ArrayList<Integer> getSumOrder(){
		return _sumOrder;
	}
	
	private ArrayList<String> guessOrdering() throws EvalException {
		
			ArrayList<Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST>>>> cptOrder = 
				getCptOrderSortedByActionVars();
			
			ArrayList<String> order = new ArrayList<String>();
			
			for ( Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST> > > cpt : cptOrder ) {

				PVAR_NAME p = cpt._o2._o1;
				CPF_DEF cpf = _state._hmCPFs.get(new PVAR_NAME(p.toString()+ "'"));
				HashMap<LVAR,LCONST> subs = new HashMap<LVAR,LCONST>();
				ArrayList<LCONST> assign = cpt._o2._o2;
				String cpt_var = CleanFluentName(p.toString() + assign);
				subs.clear();
				for (int i = 0; i < cpf._exprVarName._alTerms.size(); i++) {
					LVAR v = (LVAR)cpf._exprVarName._alTerms.get(i);
					LCONST c = (LCONST)assign.get(i);
					subs.put(v,c);
				}
						
				EXPR cpf_expr = cpf._exprEquals;
						
				HashSet<Pair> relvars = new HashSet<Pair>();
				cpf_expr.collectGFluents(subs, _state, relvars);
						
				for( Pair pa : relvars ){
					PVAR_NAME pvar = (PVAR_NAME)pa._o1;
					ArrayList<LCONST> ass = (ArrayList<LCONST>)pa._o2;
					String name = CleanFluentName(pvar.toString()+ass);
					
					if( !order.contains(name) && _state.getPVariableType(pvar) == _state.STATE ){
						order.add(name);	
					}
							
				}
						
				for( Pair pa : relvars ){
					PVAR_NAME pvar = (PVAR_NAME)pa._o1;
					ArrayList<LCONST> ass = (ArrayList<LCONST>)pa._o2;
					String name = CleanFluentName(pvar.toString()+ass);
					
					if( !order.contains(name) && _state.getPVariableType(pvar) == _state.ACTION ){
						order.add(name);	
					}
					
				}
						
				order.add(cpt_var+"'");
						
			}
			
			for( String s : _alstatevars ) {
				if( !order.contains(s) ) {
					order.add(s);
				}
			}
			
			for( String a : _alactionvars ) {
				if( !order.contains(a) ) {
					order.add(a);
				}
			}
			
			System.out.println(order);
			
			if( order.size() != 2*_alstatevars.size() + _alactionvars.size() + _alObservvars.size() ) {
				System.err.println("size of ordering not equal to size of variables");
				System.out.println(order);
				System.out.println(_alstatevars);
				System.out.println(_alactionvars);
				System.out.println(_alObservvars);
				
				HashSet<String> all = new HashSet<String>();
				all.addAll(_alactionvars);
				all.addAll(_alstatevars);
				all.addAll(_alnextstatevars);
				
				all.removeAll(order);
				
				System.out.println("ordering does not have : " + all);
				
				System.exit(1);
			}
			
			if( _debug.compareTo(DEBUG.PROBLEM_INFO) >= 0 ) {
				System.out.println("Problem has " + order.size() + " variables ");
				System.out.println("State vars : " + _alstatevars.size());
				System.out.println("Action vars : " + _alactionvars.size());
				System.out.println("Ordering : " + order);	
			}
			
			
			return order;
		}
	
	private void initADD() throws EvalException {
		
		ArrayList<String> Order = null;
		
		if( _order != ORDER.GUESS ){
			Order  = makeOrdering();
		}else{
			Order = guessOrdering();
		}
		
		createIDs(Order);//will also init _alOrder
		
		
		_context = createInstance();

		DD_ONE = _context.getConstantNode(1d);
		DD_ZERO = _context.getConstantNode(0d);
		DD_NEG_INF = _context.getConstantNode(getNegInf());
		
		_alSaveNodes = new ArrayList<Integer>();
		_alSaveNodes.add(DD_ONE);
		_alSaveNodes.add(DD_ZERO);		
	}

	private void initializeState(INSTANCE pI, DOMAIN pD, NONFLUENTS pN) {
		_state.init(pN != null ? pN._hmObjects : null, pI._hmObjects,  
				pD._hmTypes, pD._hmPVariables, pD._hmCPF,
				pI._alInitState, pN == null ? null : pN._alNonFluents, 
				pD._alStateConstraints, pD._exprReward, pI._nNonDefActions);
	}
	
	private void initRDDLData() throws EvalException {

		_state_vars  = collectStateVars();
		_action_vars = collectActionVars();
		_observ_vars = collectObservationVars();
		
		for (Map.Entry<PVAR_NAME,ArrayList<ArrayList<LCONST>>> e : _state_vars.entrySet()) {
			PVAR_NAME p = e.getKey();
			ArrayList<ArrayList<LCONST>> assignments = e.getValue();
			for (ArrayList<LCONST> assign : assignments) {
				String name = CleanFluentName(p.toString() + assign);
				_tmStateVars.put(name, new Pair(p, assign));
				_alstatevars.add(name);
				_tmNextStateVars.put(name + "'", new Pair(p, assign));
				getAlnextstatevars().add(name+"'");
			}
		}
		
		for (Map.Entry<PVAR_NAME,ArrayList<ArrayList<LCONST>>> e : _action_vars.entrySet()) {
			PVAR_NAME p = e.getKey();
			ArrayList<ArrayList<LCONST>> assignments = e.getValue();
			for (ArrayList<LCONST> assign : assignments) {
				String name = CleanFluentName(p.toString() + assign);
				_tmActionVars.put(name, new Pair(p, assign));
				_alactionvars.add(name);
			}
		}
		
		for (Map.Entry<PVAR_NAME,ArrayList<ArrayList<LCONST>>> e : _observ_vars.entrySet()) {
			PVAR_NAME p = e.getKey();
			ArrayList<ArrayList<LCONST>> assignments = e.getValue();
			for (ArrayList<LCONST> assign : assignments) {
				String name = CleanFluentName(p.toString() + assign);
				_tmObservVars.put(name + "'", new Pair(p, assign)); // SPUDD uses primed observation vars
				_alObservvars.add(name+"'");
			}
		}
		
		
	}

	/**
	 * 
	 */
	protected abstract void makePrimeRemap();

	public abstract int swapForPrimes(int dd);
		

	private ArrayList<String> makeOrdering() throws EvalException {
		
		ArrayList<String> order = new ArrayList<String>();
		
		switch(_order){
		
			case INTERLEAVE:
				
				for( String s : _alstatevars ) {
					order.add(s);
					order.add(s+"'");
				}
				
				order.addAll(_alactionvars);
				
				break;
				
			case AXPX:
				order.addAll(_alactionvars);
				order.addAll(getAlnextstatevars());
				order.addAll(_alstatevars);
				
				break;
				
			case AXXP:
				order.addAll(_alactionvars);
				order.addAll(_alstatevars);
				order.addAll(getAlnextstatevars());
				break;
			
			case XAXP:
				
				order.addAll(_alstatevars);
				order.addAll(_alactionvars);
				order.addAll(getAlnextstatevars());
				break;
				
			case XPAX:
				
				order.addAll(getAlnextstatevars());
				order.addAll(_alactionvars);
				order.addAll(_alstatevars);
				break;
				
			case XPXA:
				
				order.addAll(getAlnextstatevars());
				order.addAll(_alstatevars);
				order.addAll(_alactionvars);
				break;
			
			default:
				System.err.println("Unknown ordering...");
			
			case XXPA:
	
				
				order.addAll(_alstatevars);
				order.addAll(getAlnextstatevars());
				order.addAll(_alactionvars);
		}
		
		if( _debug.compareTo(DEBUG.PROBLEM_INFO) >= 0 ) {
			System.out.println("Problem has " + order.size() + " variables");
			System.out.println("Ordering : " + order);
		}
		
		return order;
	}
	
	public void saveThis(int dd) {
		_alSaveNodes.add(dd);
	}
	
	/**
	 * @param pCpt
	 */
	public abstract void showGraph(int dd);
	
	public String showMemory() {
		return ((RUNTIME.totalMemory()-RUNTIME.freeMemory())/1e6d) + " / " + (RUNTIME.totalMemory()/1e6d);
	}
	
	public int simplify(int dd, final Map<Integer, Boolean> assignments) {
		if( assignments == null ) {
			return dd;
		}
		
		int ret = dd;
//		Set gids = _context.getGIDs(dd);
//		int[] savedds = new int[saves.length+1];
//		System.arraycopy(saves, 0, savedds, 0, saves.length);
		
		for( Map.Entry<Integer, Boolean> entry : assignments.entrySet() ){
				ret = _context.restrict(ret, toContextID(_tmID2VarName.get(entry.getKey())), 
						entry.getValue() ? DD.RESTRICT_HIGH : DD.RESTRICT_LOW);
//				savedds[savedds.length-1] = ret;
//				flushCaches(false, savedds);
		}
			
		return ret;
	}
	
	public void makeOrders(){	
		_sumOrder.clear();
		_elimOrder.clear();
	
		Set<String> actionvars = _tmActionVars.keySet();
		
		
		try
		{
			ArrayList<Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST>>>> ret = null;
			ret = getCptOrderSortedByActionVars();
			Collections.reverse( ret );
			System.out.println(ret);
			for( Pair<Integer, Pair<PVAR_NAME, ArrayList<LCONST>>> p : ret ) {
				Pair<PVAR_NAME, ArrayList<LCONST>> pvar = p._o2;
				String var = CleanFluentName(pvar._o1.toString()+pvar._o2);
				_sumOrder.add(toID(var+"'"));
			}
		} catch (EvalException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		for( Integer varid : _alOrder ){
			String varname = _tmID2VarName.get(varid);
			if( actionvars.contains(varname) ){
				_elimOrder.add(varid);
			}
		}
			
		Collections.reverse(_elimOrder);
//		Collections.reverse(_sumOrder);
		
		if( _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ){
			System.out.println("Expectation order: " + _sumOrder);
			System.out.println("Max order: " + _elimOrder);
		}
	}

	public abstract int argMaxToNegInf(int v, int q);
//		int diff = _context.applyInt(v, q, DD.ARITH_MINUS);
//		int actzero = thresholdAt(diff, 0.0d);
//		int actone = _context.applyInt(DD_ONE, actzero, DD.ARITH_MINUS);
//		return makeNegInfDD(actone);
//	}
	
	public abstract int thresholdAt(int dd, double val);
	
	public abstract int getMaximalPath(int dd);
	
	public int fix(int dd) {
		return _context.applyInt(dd, DD_ZERO, DD.ARITH_MAX);
	}
	
	public Pair<Integer, Integer> regress(int dd, Map<Integer, Boolean> act, 
			boolean flush, final int... save){
		
		Pair<Integer, Integer> ret = regressExogenous(dd, act, flush, save);
		
		ret = regressControl(ret._o1, act, ret._o2, flush, save);
		
		if( _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ) {
			System.out.println("Regressed |V| = " + getSize(ret._o1) + " |Q| = " + getSize(ret._o2));
			System.out.println(showMemory());
		}
		
		return ret;
	}
	
	/**
	 * @param pDd
	 * @param pFlush2
	 * @param pSave
	 * @return
	 */
	private Pair<Integer, Integer> regressControl(int dd, Map<Integer, Boolean> act, int index, boolean flush, final int... save) {
		if( _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ) {
			System.out.println("Control regression " + getSize(dd));
			System.out.println(showMemory());
		}
		
		int ret = ConstrainThis(dd, act, true, flush, save);
		for( int i = index; i < _sumOrder.size(); ++i ) {
			ret = computeExpectation(ret, _sumOrder.get(i), act, flush, save);
		}
		ret = DiscountAndAddReward(ret, act, flush, save);
		int q = ret;
		saveThis(q);
		ret = maxOutActionVars(ret, act, flush, save);
		forgetThis(q);
		ret = fix(ret);
		return new Pair<Integer, Integer>(ret,q);
	}


	/**
	 * @param pAct 
	 * @param pDd
	 * @param pFlush2
	 * @param pSave
	 * @return
	 * @NOTE even though we technically dont need act here, 
	 * under full enumeration cpts are treated exogenous
	 * and need preconstraining before expectation here accordingly
	 */
	public Pair<Integer, Integer> regressExogenous(int dd, Map<Integer, Boolean> act, boolean flush, final int... save) {

		if( _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ) {
			System.out.println("Exogenous regression " + getSize(dd));
			System.out.println(showMemory());
		}
		
		int ret = ConstrainThis(dd, act, false, flush, save);
		
		ArrayList<Integer> ord = getSumOrder();
		int i;
		
		for( i = 0 ; i < ord.size(); ++i  ) {
			int id = ord.get(i);
			Integer cpt = null;
			
			try{
				cpt = getDualDiagram(id, act);
			}catch(NullPointerException e) {
				//here if id is not exo and act is null
				break;
			}
			
			if( cpt != null && isExogenous(cpt) ) {
				ret = computeExpectation(ret, id, act, flush, save);
			}else {
				break;
			}
		}
		return new Pair<Integer, Integer>(ret, i);
	}


	private Map<Integer, Boolean> toDefaultActMap(Map<Integer, Boolean> nonDefAct) {
		if( nonDefAct == null ){
			return null;
		}
		
		Map<Integer, Boolean> ret = new HashMap<Integer, Boolean>();
		
		for( String a : _alactionvars ){
			int id = _tmVarName2ID.get(a);
			if( nonDefAct.get(id) == null ){
				Pair<PVAR_NAME, ArrayList<LCONST>> rddlact = _tmActionVars.get(a);
				Boolean defval = (Boolean)_state.getDefaultValue(rddlact._o1);
				ret.put(id, defval);
			}else{
				ret.put(id, nonDefAct.get(id));
			}
		}
		return ret;
	}


	/**
	 * @param pRet
	 * @return
	 */
	public long getSize(int pRet) {
		return _context.countExactNodes(pRet);
	}


	/**
	 * @param pDd
	 * @param pAct
	 * @return
	 */
	private int computeExpectations(int dd, Map<Integer, Boolean> act, boolean flush ,
			final int... save) {
		
		int ret = dd ;
		
		ArrayList<Integer> order = getSumOrder();
		for( Integer i : order ) {
			ret = computeExpectation(ret, i, act, flush, save);
		}
		
		if( _debug.compareTo(DEBUG.DIAGRAMS ) == 0) {
			System.out.println("showing V'");
			showGraph(ret);
			try
			{
				System.in.read();
			} catch (IOException e)
			{
			}
		}
		return ret;
	}


	/**
	 * @param  
	 * @param pRet
	 * @param pI
	 * @param pAct
	 * @return
	 */
	public int computeExpectation(int dd, int id, Map<Integer, Boolean> act, 
			boolean flush, final int... save ) {
		if( _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ) {
			System.out.println("Expectation " + getVarName(id) + " " + getSize(dd) );
			System.out.println(showMemory());
		}
		
		int dual = getDualDiagram(id, act);
		int ret = _context.applyInt(dd, dual, DD.ARITH_PROD);
		ret = _context.opOut(ret, toContextID(_tmID2VarName.get(id)), DD.ARITH_SUM);
		
		if( flush ) {
			saveThis(ret);
			flushCaches(false, save);
			forgetThis(ret);	
		}
		
		return ret;
	}



	public Pair<Integer, Integer> regress(int dd, int pi, boolean flush, final int... save) {
		
		if( _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ) {
			System.out.println("Regressing via policy");
		}
		
		addPolicyConstraint(pi);
		Pair<Integer, Integer> ret = regress(dd, null, flush, save);
		removePolicyConstraint(pi);
		return ret;
	}
	
	/**
	 * @param pPi
	 */
	public abstract void addPolicyConstraint(int pi);
	
	public abstract void removePolicyConstraint(int pi);


	public abstract int approximate(int dd, double epsilon);
	
	public int DiscountAndAddReward(int dd, Map<Integer, Boolean> act, boolean flush, final int... save) {
		if( _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ) {
			System.out.println("Adding reward " + getSize(dd));
			System.out.println(showMemory());
		}
		
		int ret = _context.scalarMultiply(dd, _i._dDiscount);
		
		int rewarddd = getReward(act);
		
		ret = _context.applyInt(ret, rewarddd, DD.ARITH_SUM);
		
		ret = ConstrainThis(ret, act, true, flush, save);
		
		if( flush ) {
			saveThis(ret);
			flushCaches(false, save);
			forgetThis(ret);
		}
		
		return ret;
	}

	public int maxOutActionVars(int dd, Map<Integer, Boolean> pAss,
			boolean flush, final int... save) {
		
		
		int ret = dd;
		for( String a : _alactionvars ) {
			int id = _tmVarName2ID.get(a);
			if( pAss != null && pAss.get(id) != null ) {
				continue;
			}
			
			if( _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ) {
				System.out.println("Maxing action " + a + " |D| = " + getSize(ret));
				System.out.println(showMemory());
			}
			
			ret = _context.opOut(ret, toContextID(_tmID2VarName.get(id)), DD.ARITH_MAX);
			if( flush ) {
				saveThis(ret);
				flushCaches(false, save);
				forgetThis(ret);
			}
		}
		return ret;
	}
	
	/* (non-Javadoc)
	 * @see mdp.id.NonConcurrency#getRegressionOrder()
	 */
//	@Override
	public ArrayList<Map<Integer, Boolean>> getFullRegressionOrder() {
		ArrayList<Map<Integer, Boolean>> ret = new ArrayList<Map<Integer,Boolean>>();
		
		TreeMap<String, ArrayList<PVAR_INST_DEF>> legals;
		try
		{
			legals = ActionGenerator.getLegalBoolActionMap(_state);
		
			for( Map.Entry<String, ArrayList<PVAR_INST_DEF>> entry : legals.entrySet() ) {
				Map<Integer, Boolean> temp = getActionAssignments(entry.getValue());
				ret.add(temp);
				_hmJointActions.put(entry.getKey(), temp);
				getJointActionID(temp);
			}
		} catch (EvalException e)
		{
			e.printStackTrace();
			System.exit(1);
		}
		
		return ret;
	}


	private Map<Integer, Boolean> getActionAssignments(ArrayList<PVAR_INST_DEF> pids) {
		Map<Integer, Boolean> ret = new HashMap<Integer, Boolean>();
		for( PVAR_INST_DEF pid : pids ) {
			PVAR_NAME pvar = pid._sPredName;
			String varname = CleanFluentName(pvar.toString()+pid._alTerms);
			ret.put(toID(varname), (Boolean)pid._oValue);
		}
		return toDefaultActMap(ret);
		
	}
	
	protected abstract String toContextString(String varname);
	
	public abstract int makePolicy(int v, int q);
	
	public void forgetThis(int dd) {
		_alSaveNodes.remove((Object)dd);
	}
	
	public boolean isHorizon(int n) {
		return _i._nHorizon == n;
	}
	
	public int getHorizon() {
		return _i._nHorizon;
	}
	
	public Pair<ArrayList<Pair<Double, Double>>, Long> playDD(final int policy, final StateViz viz) {
		
		
		Policy ppolicy = new Policy() {
			
			private long ptime;
			private int numRounds;
//			private TrafficDisplay disp = new TrafficDisplay(600);
			
			@Override
			public void roundEnd(double reward) {
//				super.roundEnd(reward);
				timetoplay += (System.currentTimeMillis()-ptime);
			}
			
			@Override
			public void sessionEnd(ArrayList<Pair<Double, Double>> total_reward) {
//				super.sessionEnd(total_reward);
				timetoplay /= numRounds;
				rewards = total_reward;
			}
			
			@Override
			public void roundInit(double time_left, int horizon, int round_number, int total_rounds) {
//				super.roundInit(time_left, horizon, round_number, total_rounds);
				this.ptime = System.currentTimeMillis();
				numRounds = round_number;
			}
			
			@Override
			public ArrayList<PVAR_INST_DEF> getActions(State s) throws EvalException {
		
//				disp.getStateDescription(s);
//				disp.display(s, 0);
				
//				System.out.println(s);
				
				ArrayList assign = new ArrayList();
				
				for( Integer i : _alOrder ) {
					String varname = _tmID2VarName.get(i);
					Pair<PVAR_NAME, ArrayList<LCONST>> statevar = _tmStateVars.get(varname);
					if( statevar == null ) {
						assign.add(null);
						continue;
					}
					
					Boolean val = (Boolean)s.getPVariableAssign(statevar._o1, statevar._o2);
					assign.add(val);
				}
				
				long act = (long)Math.round(_context.evaluate(policy, assign));
				Map<Integer, Boolean> ass = _hmjointActionMap.get(act);

				if( ass == null ) {
					ass = _hmjointActionMap.get(act+1);
					try{
						ArrayList<PVAR_INST_DEF> ret = convertToRDDLAction(ass);
						s.checkStateActionConstraints(ret);
					}catch(Exception e) {
						ass = new HashMap<Integer, Boolean>();
					}
				}
				
				if( ass == null ) {
					ass = _hmjointActionMap.get(act-1);
					ArrayList<PVAR_INST_DEF> ret = convertToRDDLAction(ass);
					try{
						s.checkStateActionConstraints(ret);
					}catch(Exception e) {
						ass = new HashMap<Integer, Boolean>(); 
					}
				}
				
				ArrayList<PVAR_INST_DEF> ret = convertToRDDLAction(ass);
				
//				System.out.println(ret);
				return ret;
			}
		
		};
		
		try {
			Client cl = new Client(this._d, this._i, this._n, ppolicy, "localhost", 2316, "BEAVER!!!", viz);
		}catch(Exception e) {
			e.printStackTrace();
		}
		
		return new Pair<ArrayList<Pair<Double,Double>>, Long>(rewards, timetoplay);
	}
	
	/**
	 * @param pAss
	 * @return
	 */
	protected ArrayList<PVAR_INST_DEF> convertToRDDLAction(
			Map<Integer, Boolean> pAss) {
		ArrayList<PVAR_INST_DEF> ret = new ArrayList<RDDL.PVAR_INST_DEF>();
		for( Map.Entry<Integer, Boolean> entry : pAss.entrySet() ) {
			String varname = _tmID2VarName.get(entry.getKey());
			Pair<PVAR_NAME, ArrayList<LCONST>> rddlact = _tmActionVars.get(varname);
			Boolean defaultval = (Boolean)_state.getDefaultValue(rddlact._o1);
			if( !defaultval.equals(entry.getValue()) ) {
				ret.add(new PVAR_INST_DEF(rddlact._o1.toString(), !defaultval, rddlact._o2));	
			}
			
		}
		return ret;
	}


	public void setValueDD(int dd){
		_valueDD = dd;
	}
	
	public void setPolicyDD(int dd) {
		_policyDD = dd;
	}
	
	public abstract Pair<D, TreeMap<Integer, String>> getNewContext();
	
	protected TreeMap<Integer, String> toContextIDs(TreeMap<String, Integer> varName2ID) {
		TreeMap<Integer, String> ret = new TreeMap<Integer, String>();
		for( Map.Entry<String, Integer> entry : varName2ID.entrySet() ){
			ret.put(toContextID(entry.getKey()), entry.getKey() );
		}
		return ret;
	}
	
	public abstract String getVarName(int id) ;
	
	public int getInitialPolicy(){
//		int ind = (int)(Math.random()*_alactionvars.size());
		String act = _alactionvars.get(0);
		return _context.getVarNode(toContextID(act), getNegInf(), 0.0);
	}
	
	public int toID(String s) {
		return _tmVarName2ID.get(s);
	}


	/**
	 * @param alnextstatevars the alnextstatevars to set
	 */
	public void setAlnextstatevars(ArrayList<String> alnextstatevars) {
		this._alnextstatevars = alnextstatevars;
	}


	/**
	 * @return the alnextstatevars
	 */
	public ArrayList<String> getAlnextstatevars() {
		return _alnextstatevars;
	}
	
	public ArrayList<String> getMaxOrderString(){
		ArrayList<String> ret = new ArrayList<String>();
		ArrayList<Integer> intOrd = getMaxOrder();
		for( Integer i : intOrd ) {
			ret.add(_tmID2VarName.get(i));
		}
		return ret;
	}
	
	public ArrayList<String> getSumOrderString(){
		ArrayList<String> ret = new ArrayList<String>();
		ArrayList<Integer> intOrd = getSumOrder();
		for( Integer i : intOrd ) {
			ret.add(_tmID2VarName.get(i));
		}
		return ret;
	}
	
	public ArrayList<Map<Integer, Boolean>> convertToBN(Map<Integer, Boolean> from,
			Integer newAss, boolean flush, final int... save) {
		
		ArrayList<Map<Integer,Boolean>> regOrder = null;
		if( BN_ON || ( from != null && from.size() == 0 ) ) {
			regOrder = specialize(from, newAss);
		}
		else if( from == null ) {
			
			regOrder = getFullRegressionOrder();
			for( Map<Integer, Boolean> jointact : regOrder ) {
				Pair< Map<Integer, Integer>, Integer > DDs 
					= getDualDiagrams(jointact);
				bigBookOfTrans.put(jointact, DDs._o1);
				bigBookOfRewards.put(jointact, DDs._o2);
			}
		}
		
		if( bigBookOfTrans.get(null) == null ) {
			Map<Integer,Integer> exo = new HashMap<Integer, Integer>(); 
			
			for( int i : _sumOrder ) {
				int dd = getDualDiagram(i, null);
				if( isExogenous(dd) ) {
					exo.put(i,dd);
//					System.out.println(getVarName(i));
				}
			}
			bigBookOfTrans.put(null, exo);
			
		}

		
		BN_ON  = true;
		
		if( flush ) {
			flushCaches(false, save);	
		}
		
		return regOrder;
	}
	
	/**
	 * @param pFrom
	 * @param pNewAss
	 * @return
	 */
	private ArrayList<Map<Integer, Boolean>> specialize(
			Map<Integer, Boolean> from, Integer newAss) {
		
		Integer rew = bigBookOfRewards.get(from);
		rew = rew == null ? _rewardDD : rew;
		
		Map<Integer, Integer> transDDs = bigBookOfTrans.get(from);
		transDDs = transDDs == null ? _var2transDD : transDDs;
		
		specializeHelper(rew, transDDs, newAss, true, from);
		specializeHelper(rew, transDDs, newAss, false, from);
		
		bigBookOfRewards.remove(from);
		bigBookOfTrans.remove(from);
		
		ArrayList<Map<Integer, Boolean>> specials = new ArrayList<Map<Integer,Boolean>>();
		HashMap<Integer, Boolean> thisAssign = new HashMap<Integer, Boolean>(from);
		thisAssign.put(newAss, true);
		specials.add(thisAssign);
		
		thisAssign = new HashMap<Integer, Boolean>(from);
		thisAssign.put(newAss, false);
		specials.add(thisAssign);
		
		return specials;
	}


	/**
	 * @param from 
	 * @param pRew
	 * @param pTransDDs
	 * @param pNewAss
	 * @param pB
	 */
	private void specializeHelper(Integer rew,
			Map<Integer, Integer> transDDs, int newAss, boolean value, Map<Integer, Boolean> from) {
		
		Map<Integer, Boolean> thisAssign = new HashMap<Integer, Boolean>(from);
		thisAssign.put(newAss, value);
		
		Map<Integer, Boolean> singleTon = new HashMap<Integer, Boolean>();
		singleTon.put(newAss, value);
		
		Integer r = bigBookOfRewards.get(thisAssign);
		if( r == null ) {
			r = simplify(rew, singleTon);
		}

		Map<Integer, Integer> trueDDs = bigBookOfTrans.get(thisAssign);
		if(  trueDDs == null ) {
			trueDDs = new HashMap<Integer, Integer>(transDDs.size());
			for( Map.Entry<Integer, Integer> entry : transDDs.entrySet() ) {
				trueDDs.put(entry.getKey(), simplify(entry.getValue(), singleTon));
			}	
		}

		bigBookOfRewards.put(thisAssign, r);
		bigBookOfTrans.put(thisAssign, trueDDs);	
		
	}


	/**
	 * @param pJointact
	 * @return
	 * @NOTE use only with full regression order
	 */
	private Pair< Map<Integer, Integer> , Integer > getDualDiagrams(
			Map<Integer, Boolean> pJointAct) {
		
		Map<Integer, Integer> retTrans = new HashMap<Integer, Integer>();
		
		for( int xp : _sumOrder ) {
			int transdd = getDualDiagram(xp, pJointAct);
			retTrans.put(xp, transdd);
		}
		
		int rew = getReward(pJointAct);
		
		return new Pair<Map<Integer,Integer>, Integer>(retTrans, rew);
	}


	public boolean isExogenous(int pCpt) {
		Set gids = _context.getGIDs(pCpt);
		for( Object o : gids ) {
			String var = getActualVarName((Integer)o);
			if( _alactionvars.contains(var) ) {
				return false;
			}
		}
		return true;
	}
	
	public ArrayList<Pair<Integer, String>> getScores(Map<Integer, Boolean> ass, 
			int sumInd) {
		
		ArrayList<Pair<Integer, String>> ret = new ArrayList<Pair<Integer,String>>();
		HashMap<String, Integer> tempScores  = new HashMap<String, Integer>();
		
		for( int i = 0; i < sumInd; ++i ) {
			String ns = getVarName(_sumOrder.get(i));
			ArrayList<String> the = _hmvars2act.get(ns);
			
			if ( the == null ) {
				continue;
			}
			
			for( String a : the ) {
				Integer sc = tempScores.get(a);
				int actID = toID(a);
				
				if( sc == null ) {
					sc = new Integer(0);
				}
				
				++sc;
				
				if( ass.get(actID) == null ) {
					tempScores.put(a, sc);
				}
				
			}
		}
		
		for( Map.Entry<String, Integer> entry : tempScores.entrySet() ) {
			ret.add( new Pair<Integer, String>(entry.getValue(), entry.getKey()) );
		}
		
		Collections.sort(ret);
		Collections.reverse(ret);
		return ret;
		
	}


	public int evaluatePolicy(int pi, int evalLength) {
		Pair<Integer, Integer> vq = new Pair<Integer, Integer>(DD_ZERO, DD_ZERO);
		int Vprev = DD_ZERO; 
		
        for( int i = 0 ; i < evalLength; ++i ){

	        	if( _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ){
	        		Vprev = vq._o1;
	        		saveThis(Vprev);
	        	}
	        	
                int v = swapForPrimes(vq._o1);
                vq = regress(v, pi, true, pi);
                System.out.println("Eval iter  " + i );
                
                if( _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ){
                	System.out.println(getMax(_context.applyInt(vq._o1, Vprev, DD.ARITH_MINUS)));
                }
        }
        return vq._o1;
	}
	
	public int improvePolicy(int v, final int... save) {
		
		v = swapForPrimes(v);
        if( _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ){
                System.out.println("Improving policy " + getSize(v));
        }        

        Pair<Integer, Integer> vq = regress(v, null, true, save);

        if( _debug.compareTo(DEBUG.SOLUTION_INFO) >= 0 ){
                System.out.println("Arg maxing " + getSize(vq._o2));
        }

        int pid = argMaxToNegInf(vq._o1, vq._o2);
        
        return pid;
        
	}
	
	
	/**
	 * @param pDd
	 * @param pAssign
	 * @return
	 */
	public abstract int makePolicyHelper(int pDd, Map<String, Boolean> pAssign,
			ArrayList<Pair<Double, Double>> affine);
	
	public abstract int getADD(int dd, ADDHandler handl);
}
